package shutdown

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"gitee.com/githubdog/goci/tools/helper"
)

type CustomPort struct {
	Port        string `json:"port,omitempty"`
	Tag         string `json:"tag,omitempty"`
	Title       string `json:"title,omitempty"`
	Kill        string `json:"kill,omitempty"`
	Restart     string `json:"restart,omitempty"`
	BuildSource string `json:"build_source,omitempty"`
	BuildTarget string `json:"build_target,omitempty"`
	LogPath     string `json:"log_path,omitempty"`
}

var (
	portMap = map[string]CustomPort{}
	cmdMap  = map[string]string{
		"1": "优雅重启",
		"2": "优雅关闭",
		"3": "运行程序",
		"4": "查看PID",
	}
	input *bufio.Scanner
)

func setPortMap(portPath string) error {
	var (
		err       error
		dir       string
		fileBytes []byte
	)
	if dir, err = helper.CheckProtectDir(); err == nil {
		if dir == "" {
			return errors.New("the project root path could not be identified")
		} else if fileBytes, err = ioutil.ReadFile(dir + portPath); err == nil {
			err = json.Unmarshal(fileBytes, &portMap)
		}
	}
	// portMap = map[string]customPort{
	// 	"8012": {
	// 		Title:       "[HTTP] PC端web服务",
	// 		Kill:        "-QUIT",
	// 		Restart:     "-HUP",
	// 		BuildSource: "./startup/http/index.go",
	// 		BuildTarget: "./startup/http/cmd",
	// 		LogPath:     "./logs/http.log",
	// 	},
	// 	"8014": {
	// 		Title:       "[HTTP] 移动端web服务",
	// 		Kill:        "-QUIT",
	// 		Restart:     "-HUP",
	// 		BuildSource: "./mobile/startup/index.go",
	// 		BuildTarget: "./mobile/startup/cmd",
	// 		LogPath:     "./logs/mobile.log",
	// 	},
	// }
	return err
}

func RunAllProcess(portFile string) {
	// port
	if err := setPortMap(portFile); err != nil {
		panic(err)
	}
	if len(portMap) == 0 {
		helper.PrintWithColor("未配置端口信息", 0)
		return
	}
	input = bufio.NewScanner(os.Stdin)
	helper.PrintWithColor("请在项目根目录处运行此程序~~~~~~~~~", 2)
	helper.PrintWithColor("可选择的端口", 3)
	fmt.Println("所有端口: all")
	keyArr := []string{}
	for k, v := range portMap {
		fmt.Printf("%s: %s\n", v.Title, k)
	}
	helper.PrintWithColor("可选择的操作指使", 3)
	for k := range cmdMap {
		keyArr = append(keyArr, k)
	}
	sort.Strings(keyArr)
	for _, v := range keyArr {
		fmt.Println(v+").", cmdMap[v])
	}
	helper.PrintWithColor("格式: [是否编译(#?)][端口]-[操作指令],..。eg: #8012-1,8013-2,#8014-3", 3)
	checkInupt()
	helper.PrintWithColor("Bye~~", 1)
}

func checkInupt() {
	fmt.Print("请输入魔法命令:")
	// reg := regexp.MustCompile(`^\s*(\#?)\s*((?:all)|[\d\+]+)\s*-\s*(\d+)\s*`)
	reg := regexp.MustCompile(`^\s*(\#?)\s*([^\-]+)\s*-\s*(\d+)\s*`)
	for input.Scan() {
		_input := strings.Trim(input.Text(), " \r\n")
		checkExit(_input)
		okNumber := 0
		for _, item := range strings.Split(_input, ",") {
			if item != "" {
				if matchs := reg.FindStringSubmatch(item); len(matchs) >= 4 {
					if matchs[1] != "" && matchs[1] != "#" {
						helper.PrintWithColor("编译命令无效:"+item, 0)
					} else if ports := getSelectPorts(matchs[2]); len(ports) == 0 {
						helper.PrintWithColor("端口无效:"+item, 0)
					} else if cmdStr, ok := cmdMap[matchs[3]]; !ok {
						helper.PrintWithColor("操作指定无效:"+item, 0)
					} else {
						helper.PrintWithColor(fmt.Sprintf("[%s]%s", item, cmdStr), 1)
						if matchs[1] == "#" {
							for _, val := range ports {
								buildFile(val)
							}
						}
						for k, v := range ports {
							handleProcess(matchs[3], k, v)
						}
						okNumber++
					}
				} else {
					helper.PrintWithColor("魔法命令不合法:"+item, 0)
				}
			}
		}
		if okNumber > 0 {
			break
		} else {
			fmt.Print("请输入魔法命令:")
		}
	}
}

func getSelectPorts(str string) map[string]CustomPort {
	ret := map[string]CustomPort{}
	if str == "all" {
		ret = portMap
	} else {
		for _, val := range strings.Split(str, "+") {
			val = strings.TrimSpace(val)
			if val != "" {
				if v, ok := portMap[val]; ok {
					ret[val] = v
				} else {
					for key2, val2 := range portMap {
						if checkContains(val2.Tag, val) {
							ret[key2] = val2
						}
					}
				}
			}
		}
	}
	return ret
}

func buildFile(item CustomPort) {
	cmdStr := "go build -o " + item.BuildTarget + " " + item.BuildSource
	runCommand(cmdStr, true)
}

func runProcess(item CustomPort) {
	if !helper.PathExists(item.BuildTarget) {
		buildFile(item)
	}
	cmdStr := fmt.Sprintf(`nohup %s >> %s 2>&1 &`, item.BuildTarget, item.LogPath)
	runCommand(cmdStr, true)
}

// 操作进程
func handleProcess(_input string, port string, item CustomPort) {
	if _input == "1" {
		if pid := outputCmd(port); pid != "" {
			if item.Restart != "" {
				handleKillCmd([]string{item.Restart, pid})
			} else {
				handleKillCmd([]string{item.Kill, pid})
				runProcess(item)
			}
		} else {
			runProcess(item)
		}
	} else if _input == "2" {
		if pid := outputCmd(port); pid != "" {
			handleKillCmd([]string{item.Kill, pid})
		}
	} else if _input == "3" {
		runProcess(item)
	} else if _input == "4" {
		if pidStr := outputCmd(port); pidStr != "" {
			helper.PrintWithColor("正在运行中...", 1)
		} else {
			helper.PrintWithColor("进程未开启...", 2)
		}
	}
}

func checkExit(_input string) {
	if strings.ToLower(_input) == "exit" {
		helper.PrintWithColor("Bye~~", 1)
		os.Exit(0)
	}
}

func runCommand(cmdStr string, isPanic bool) (string, error) {
	fmt.Printf("命令执行中[%s], 请稍等...\n", cmdStr)
	cmd := exec.Command("/bin/sh", "-c", cmdStr)
	bts, err := cmd.Output()
	out := ""
	if err != nil {
		if isPanic {
			panic(err)
		}
	} else {
		out = string(bts)
		fmt.Printf("outputCmd:\n%s", out)
	}
	return out, err
}

func handleKillCmd(cmdArgs []string) {
	if len(cmdArgs) > 0 {
		fmt.Println("kill", strings.Join(cmdArgs, " "))
		if err := exec.Command("kill", cmdArgs...).Start(); err != nil {
			helper.PrintWithColor(err.Error(), 0)
		} else {
			helper.PrintWithColor("操作成功", 1)
		}
	}
}

func outputCmd(port string) (pid string) {
	// args := []string{"-nP", "-iTCP:" + port, "-sTCP:LISTEN|grep", "-v", "grep|awk", "'{print $2}'"}
	// if bts, err := exec.Command("lsof", args...).Output(); err != nil {
	cmdStr := "lsof -nP -iTCP:" + port + " -sTCP:LISTEN | grep -v grep | awk '{print $2}'"
	str, _ := runCommand(cmdStr, true)
	str = strings.ReplaceAll(str, "\\ ", "")
	// if ret := regexp.MustCompile(`[^ \r\n]+\ +(\d+)\ +`).FindStringSubmatch(str); len(ret) >= 2 {
	if ret := regexp.MustCompile(`\b(\d+)\b`).FindStringSubmatch(str); len(ret) >= 2 {
		if nPid, err := strconv.Atoi(ret[1]); err == nil && nPid > 0 {
			pid = strings.Trim(ret[1], " \r\n")
		}
	}
	return
}
